﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Win32;
using System.IO;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Threading;
using System.Net;
using ICSharpCode.SharpZipLib.BZip2;

namespace CygwinPackageManager.Common
{
	public sealed class InstallationManager
	{
		[MethodImpl(MethodImplOptions.Synchronized)]
		private static InstallationInfo _GetInstallationInfo()
		{
			// Variables.
			bool isNew = false;
			bool isCorrupt = false;
			bool isReady = true;
			bool? allUsers = null;
			string rootDirectory = string.Empty;
			string cacheDirectory = string.Empty;
			Uri lastMirrorUri = null;

			#region IsNew
			// Find root directory and AllUsers flag.
			rootDirectory = (string)Registry.GetValue(Utils.GetRegistryKey(true), "native", string.Empty);
			if (string.IsNullOrEmpty(rootDirectory))
			{
				rootDirectory = (string)Registry.GetValue(Utils.GetRegistryKey(false), "native", string.Empty);
				if (string.IsNullOrEmpty(rootDirectory))
				{
					// Can't find root directory, consider this a new installation and proceed no further.
					isNew = true;
				}
				else
				{
					allUsers = false;
				}
			}
			else
			{
				allUsers = true;
			}
			#endregion

			if (!isNew)
			{
				#region IsCorrupt

				// Find cache directory.
				bool cacheExists = true;
				try
				{
					cacheDirectory = File.ReadAllText(Utils.GetLastCachePath(rootDirectory)).ExtendedTrim();
				}
				catch
				{
					cacheExists = false;
				}

				if (!cacheExists || !Directory.Exists(cacheDirectory))
				{
					isCorrupt = true;
				}

				// Find last mirror.
				try
				{
					lastMirrorUri = new Uri(File.ReadAllText(Utils.GetLastMirrorPath(rootDirectory)).ExtendedTrim());
				}
				catch
				{
					isCorrupt = true;
				}

				if (lastMirrorUri == null)
				{
					isCorrupt = true;
				}
				#endregion
			}
			else
			{
				// If IsNew, set IsCorrupt too.
				isCorrupt = true;
			}

			if (isNew || isCorrupt)
			{
				isReady = false;
			}
			else
			{
				string setupPath = Utils.GetSetupIniPath(cacheDirectory, lastMirrorUri.OriginalString);
				isReady = File.Exists(setupPath);
			}

			// Create a new installtion with defaults.
			return new InstallationInfo()
			{
				AllUsers = allUsers,
				CacheDirectory = cacheDirectory,
				IsCorrupt = isCorrupt,
				IsNew = isNew,
				IsReady = isReady,
				LastMirrorUri = lastMirrorUri,
				RootDirectory = rootDirectory,
			};
		}
		private static InstallationInfo _currentInstallationInfo;
		private static InstallationInfo CurrentInstallationInfo
		{
			[MethodImpl(MethodImplOptions.Synchronized)]
			get
			{
				if (_currentInstallationInfo == null)
					_currentInstallationInfo = _GetInstallationInfo();

				return _currentInstallationInfo;
			}
			[MethodImpl(MethodImplOptions.Synchronized)]
			set { _currentInstallationInfo = value; }
		}

		[MethodImpl(MethodImplOptions.Synchronized)]
		public static void PrepareInstallation()
		{
			InstallationInfo installation = CurrentInstallationInfo;

			if (installation.IsNew || installation.IsCorrupt)
				throw new InvalidOperationException();

			string mirrorUri = installation.LastMirrorUri.OriginalString;
			string cache = installation.CacheDirectory;
			string setupPath = Utils.GetSetupIniPath(cache, mirrorUri);
			string setupDir = Utils.GetSetupIniDirectory(cache, mirrorUri);

			if (!File.Exists(setupPath))
			{
				byte[] data = null;
				using (WebClient wc = new WebClient())
				{
					try
					{
						data = wc.DownloadData(string.Concat(mirrorUri, @"\setup.bz2"));
					}
					catch
					{
						throw;
					}
				}

				try
				{
					if (!Directory.Exists(setupDir))
						Directory.CreateDirectory(setupDir);

					using (MemoryStream ms = new MemoryStream(data))
					using (FileStream fs = new FileStream(setupPath, FileMode.CreateNew))
						BZip2.Decompress(ms, fs);
				}
				catch
				{
					if (File.Exists(setupPath))
						File.Delete(setupPath);

					throw;
				}
			}

			installation.IsReady = true;
		}

		public static InstallationInfo GetInstallationInfo()
		{
			return CurrentInstallationInfo;
		}

		/// <summary>
		/// Saves installation information.
		/// </summary>
		/// <param name="rootDirectory"></param>
		/// <param name="cacheDirectory"></param>
		/// <param name="mirrorUri"></param>
		/// <param name="allUsers"></param>
		/// <returns>True if installation has changed.</returns>
		[MethodImpl(MethodImplOptions.Synchronized)]
		public static bool SaveInstallationInfo(string rootDirectory, string cacheDirectory, string mirrorUri, bool allUsers)
		{
			if (string.IsNullOrEmpty(rootDirectory) || !Directory.Exists(rootDirectory))
				throw new ArgumentException();

			if (string.IsNullOrEmpty(mirrorUri))
				throw new ArgumentException();

			bool installationChanged = false;

			InstallationInfo installation = CurrentInstallationInfo;
			if (installation.IsNew)
			{
				// TODO: Install cygwin.
			}

			// Cache.
			if (cacheDirectory != installation.CacheDirectory)
			{
				File.WriteAllText(Utils.GetLastCachePath(rootDirectory), cacheDirectory);
				installationChanged = true;
			}

			// Last Mirror.
			if (mirrorUri != installation.LastMirrorUri.OriginalString)
			{
				File.WriteAllText(Utils.GetLastMirrorPath(rootDirectory), mirrorUri);
				installationChanged = true;
			}

			// Reset installation information.
			if (installationChanged)
			{
				CurrentInstallationInfo = null;
			}

			return installationChanged;
		}
	}
}
